/**
 * @file proto_plat.c
 * @author ZMC (czm@comtika.com)
 * @brief protocol for client.
 * @version 1.0.0.2
 * @date 2024-11-19
 * 
 * 

 * @version 1.0.0.0
 * @date 2024-07-05

uint16_t tlv_login(uint8_t* buf)
{
    sprintf(login.softver,"v%04x-%s",app_get_ver(),__DATE__);

  //  login.sub_type = CTK_PID;////子设备类型	设备直接联网时有效（见附录A）;网关集中器登录时该值填0。
    login.sub_type = CCT_PID;   //----dan----

}



 ///////------上行协议的核心处理句柄----------------------
void tlv_recv_handler(uint8_t*rx_flag, uint8_t*rxbuf, uint16_t rxlen, uint8_t src)
{
 //   uint16_t pb_rxlen = pf_rx->body_lenH << 8 | pf_rx->body_lenL;   //移位发送溢出
    uint16_t pb_rxlen = pf_rx->body_lenH ;   //避免移位发送溢出
     pb_rxlen = pb_rxlen << 8 | pf_rx->body_lenL;   
}

ptlv_t tlv_check(uint8_t* data, uint16_t len)
{
/////-------dan-------------////
}



////---dan---
typedef enum{
    tlv_head2_net_CCT  = 0x00,    //0=网关集中器联网
    tlv_head2_net_NODE = 0x01,    //1=设备直接联网  

}tlv_head2_net_e;



  ////只对帧头进行封装---数据域不变---最后加上校验
uint16_t tlv_seal(ptlv_t buf, uint8_t cmd, uint8_t seq, uint16_t body_len)
{

// 报文头内容定义格式如下：
// 编号	长度	名称	说明
// 1	1字节	协议版本号	通讯协议主版本号，例如v3.0.0版本号为0x03
// 1	1字节	序列号  	报文帧序号。
// 报文的发送方需保证连续发送的报文其序号依次递增；响应方回复时需保证与发送方保持一致。
// 2	1字节	特殊标识 	Bit0：通信方向。 1=server->client, 0=client->server
// Bit1：1=报文体经过AES 算法加密
// Bit2：0=网关集中器联网，1=设备直接联网   
// 3	1字节	命令码	控制或应答指令。
// 注意：心跳包的报文头到此结束，共4个字节，后面的扩展内容不传输，目的为了节省流量。
// 以下为扩展内容
// 4	4字节	时间戳	当前系统时间戳，精确到秒。
// 响应方回复时需保证与发送方保持一致。


 ////////只对帧头进行封装---数据域不变---最后加上校验
    buf->head = TLV_HEAD;     //帧头
    buf->head_len = sizeof(tlv_head_t);
    buf->body_lenH = (uint8_t)(body_len >> 8);
    buf->body_lenL = (uint8_t)body_len;
    
    ptlv_head_t ph = (ptlv_head_t)buf->data;
    ph->version = TLV_VERSION;
    ph->seq = seq;//序列号 ,依次递增,保证与发送方保持一致

    ph->flag.AES = 0;// Bit1：1=报文体经过AES 算法加密
    ph->flag.dir = 0;// 0=client->server

   // ph->flag.net = 1;// Bit2：0=网关集中器联网，1=设备直接联网  
    ph->flag.net = tlv_head2_net_CCT;// Bit2：0=网关集中器联网，1=设备直接联网  

    ph->flag.unsupported = 0;
    ph->flag.res = 0;
    ph->cmd = cmd;

    uint16_t totol_len = sizeof(tlv_t) + sizeof(tlv_head_t) + body_len;
    uint16_t crc = iotv30_crc16_modbus((uint8_t*)buf, totol_len);

    // buf->data[sizeof(tlv_head_t) + body_len] = (uint8_t)(crc >> 8);
    // buf->data[sizeof(tlv_head_t) + body_len + 1] = (uint8_t)crc;

    buf->data[sizeof(tlv_head_t) + body_len] = (uint8_t)crc;
    buf->data[sizeof(tlv_head_t) + body_len + 1] = (uint8_t)(crc >> 8);


#if  PROTO_PLAT_PRINT_ENABLE      //本文件调试打印使能

    printf("%s\n",__FUNCTION__);

    printf("head=%02x\n", buf->head);     //帧头
    printf("ver=%d\n", ph->version);   
    printf("seq=%d\n", ph->seq);    
    printf("cmd=%02x\n",ph->cmd);

   if( ph->flag.dir )
   {
     printf("server->client\n");////1=server->client
   }else{
     printf("client->servert\n");  //// 0=client->server
   }

   if( ph->flag.AES )
   {
     printf("ciphertext\n");//1=报文体经过AES 算法加密
   }else{
     printf("plaintext\n");  //// 0=明文---未经加密
   }

   if( ph->flag.net )
   {
     printf("Node\n");////1=设备直接联网 
   }else{
     printf("Gateway\n");  //// Bit2：0=网关集中器联网
   }


    printf("body_data_pb_format: "); 
    uint idx=buf->head_len;
    for (uint i = 0; i < body_len; i++)
    {
        printf("%02X", buf->data[idx++]);
    }
    printf("\n");


#endif

    return totol_len + 2;

}




/////----拷贝数据到通讯帧的数据域部分----
void tlv_pack_load_body(ptlv_t buf, uint8_t*body, uint16_t body_len)
{
    memcpy(&buf->data[sizeof(tlv_head_t)], body, body_len);
}


/////----获取通讯帧的数据域首地址的指针----
uint8_t* tlv_pack_get_body(ptlv_t buf)
{
    return &buf->data[sizeof(tlv_head_t)];
}





static inline uint16_t pb_get_dev_attr(pb_istream_t* istream, pb_ostream_t* ostream)
{
......
        //updata.data.sub_uid = 0;  // bug 
        updata.data.sub_uid = tmp.sub_uid;  //debug ---dan---
   
        if(tmp.read_type != 0){//按类型读取

            updata.data.props_count =  dev_module_read_attr(  tmp.sub_uid, tmp.read_type, &updata.data.props[0]) ;

            if(pb_encode(ostream, GetSubIotDataUp_fields, &updata)){
                return ostream->bytes_written;
            }
        
        }else{//读取指定测量点
...
                dev_module_get_attr(tmp.sub_uid,tmp.iot_ids[i], &updata.data.props[i]);
.....
}




 * @version 1.0.0.2
 * @date 2024-11-19

 //@brief 0xF1命令码处理，心跳

static inline void pb_heart_rx(pb_istream_t* istream)
{
    LOG_NAME();
    hplat_comm.heartbeat_cnt = 0;

    if(pb_ack_decode(istream) != tlv_ack_ok)
    {
        //错误应答重新发送登录包
        hplat_comm.login = 0;
        hplat_comm.login_cnt = 0;
    }


}




  方法tlv_recv_handler()内部的大数组移到外面作为本地全局数组

  tlv_recv_handler()
  {
     uint8_t  txbuf[TLV_TXBUF_SIZE];
  }

  static uint8_t  tlv_txbuf[TLV_TXBUF_SIZE];




 --------------------------------------*/


#include "cjson_api_readAttr.h"
#include "gui_cjson_api.h"
#include "gui_logist.h"
#include "iotv30_config.h"
#include <stdint.h>
#include <stdio.h>


#include "dev_manager.h"
#include "iot_node.h"
#include "iot_node_func.h"
#include "led.h"
#include "mconfig.h"

#include "platform.h"

#include "misc_tools.h"
#include "rtc_calendar.h"
#include "stdbool.h"

#include "pb_common.h"
#include "pb_encode.h"
#include "pb_decode.h"
#include "iot_message.pb.h"

#include "simple_event.h"
#include "logfile.h"
#include "platform.h"
#include "system.h"
#include "time_taskv30.h"
#include "uart_printf.h"
#include "user_flash.h"
#include "user_nv.h"
#include "test_mode.h"
#include "proto_upload_file.h"

#include "dev_module.h"
//  #include "dev_attr.h"

#include "cm_time.h"
#include "iotv30_app.h"


#include "cm_fs.h"
//#include "bsp_4g.h"
#include "dev_4g.h"

#include "ota_api.h"

#include  "iotv30_main.h"

#include "pb_kt_ota_device.h"

#include "proto_plat.h"



plat_comm_t hplat_comm;



static uint8_t seq=0;

static uint8_t  tlv_txbuf[TLV_TXBUF_SIZE];


/**
 * @brief 主动发送数据给平台时获取seq?????
 * 
 * @return uint8_t 
 */
uint8_t tlv_get_seq(void)
{
    return seq++;
}



////=======登录心跳逻辑============
void plat_conn_manager(void)
{
    static uint32_t last_login_time,last_heartbeat_time;
    uint8_t txbuf[128];

#if 0
    if(hcat1.network_state != network_connected){
        return;
    }
#endif

    // if( 0==iotv30_IsTcpClientConnected() ){
    //     return;
    // }




  // LOG_NAME();

    /*send login*/
    if(!hplat_comm.login){
        if( !hplat_comm.login_cnt || ((hplat_comm.login_cnt) && (get_runtime() - last_login_time > 30000)) ){//30sec

            uint16_t txlen = tlv_login(txbuf);//打包登录包数据---OK

            plat_send(txbuf, txlen);  //发送TCP数据----Segmentation fault (core dumped)

            hplat_comm.login_cnt++;
            last_login_time = get_runtime();

            printf( "send login_cnt=%d,login_time=%d\n", hplat_comm.login_cnt,last_login_time);//ok

         //   cm_log_printf(0, "send login %d\n", hplat_comm.login_cnt);

            if(hplat_comm.login_cnt >= 10){//5min

                cat1_reconnect("can not login");

                hplat_comm.login_cnt = 0;

                close(ClientInfo[TCP_API_CH_IOTV30].fd );
            }
        }
        return;
    }

    /*send heart beat*/
    if(get_runtime() - hplat_comm.last_conn_time > TLV_HeartbeatPeriod_ms){//58s
        if(get_runtime() - last_heartbeat_time > TLV_HeartbeatPeriod_ms){
            if(hplat_comm.heartbeat_cnt < TLV_heartbeat_retry_max){
                last_heartbeat_time = get_runtime();
                uint16_t txlen = tlv_heartbeat(txbuf);
                plat_send(txbuf, txlen);
                hplat_comm.heartbeat_cnt++;
                cm_log_printf(0, "send hearbeat\n");
            }else{
                hplat_comm.heartbeat_cnt = 0;
                hplat_comm.login = 0;
                //LED_Link_Off( );   
                cat1_reconnect("heartbeat reach maximum");

                close(ClientInfo[TCP_API_CH_IOTV30].fd ); // 心跳超时连接断开
            }
        }
    }
}




/**
 * @brief 解析仅含有ack的消息
 * 
 * @param istream 
 * @return uint8_t 
 */
static uint8_t pb_ack_decode(pb_istream_t* istream)
{
    ReplyAck reply;
    memset(&reply, 0 , sizeof(ReplyAck));
    
    pb_decode(istream, ReplyAck_fields, &reply);
    return reply.ack;
}


/**
 * @brief 编码仅含有ack的消息
 * 
 * @param postream 
 * @param ack 
 * @return uint16_t 
 */
uint16_t pb_ack_encode(pb_ostream_t* postream, uint8_t ack)
{
    ReplyAck reply;
    memset(&reply, 0 , sizeof(ReplyAck));
    reply.ack = ack;
    if(pb_encode(postream, ReplyAck_fields, &reply))
    {
        return postream->bytes_written;
    }
    return 0;
}

/***************************************************************************************************** */

/**
 * @brief 0xF0命令码处理，登录
 * 
 * @param
 */
static inline void pb_login_rx(pb_istream_t* istream)
{
    LOG_NAME();

    if(pb_ack_decode(istream) == tlv_ack_ok)
    {
        hplat_comm.login = 1;
        hplat_comm.login_cnt = 0;        
        LED_Link_On( );
    }
}

/**
 * @brief 0xF1命令码处理，心跳
 * 
 * @param
 */
static inline void pb_heart_rx(pb_istream_t* istream)
{
    LOG_NAME();
    hplat_comm.heartbeat_cnt = 0;

    if(pb_ack_decode(istream) != tlv_ack_ok)
    {
        printf("hear_beat_ack_error! relogin\n");
        //错误应答重新发送登录包
        hplat_comm.login = 0;
        hplat_comm.login_cnt = 0;
    }

}


/**
 * @brief 0xF2复位
 * 
 * @param istream 
 * @param src 
 */
static inline uint16_t pb_reset_rx(pb_istream_t* istream, pb_ostream_t* ostream)
{
    RestartDown reset;
    memset(&reset, 0, sizeof(RestartDown));

    LOG_NAME();

    if(pb_decode(istream, RestartDown_fields, &reset))
    {
        //0,重启集中器，1重启plc模块，直接联网的设备无需判断
        if(reset.module == 0)
        {
            //异步重启系统
            if(AsyncReboot() != 0) {
                return pb_ack_encode(ostream, tlv_ack_busy);
            }

        }


   //     logfile_write(1,"ret by server");
   //     simple_event_send(event_reset, 200);

        return pb_ack_encode(ostream, tlv_ack_ok);
        // return tlv_ack()
    }


    cm_log_printf(0, "f2 pb decode failed\n");

    return 0;

}

/**
 * @brief 0xF3查ip、port
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static inline uint16_t pb_get_server_addr(pb_istream_t* istream, pb_ostream_t* ostream)
{
    GetServerAddrUp tmp;
    memset(&tmp, 0, sizeof(GetServerAddrUp));

    LOG_NAME();

    tmp.has_address = true;
    strcpy(tmp.address.ip, user_config.ip);
    tmp.address.port = user_config.port;

    if (pb_encode(ostream, GetServerAddrUp_fields, &tmp)) {
        return ostream->bytes_written;
    }
    return 0;
}

/**
 * @brief  F4
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static inline uint16_t pb_set_server_addr(pb_istream_t* istream, pb_ostream_t* ostream)
{
    SetServerAddrDown tmp;
    memset(&tmp, 0, sizeof(SetServerAddrDown));

    LOG_NAME();   

    if (pb_decode(istream, SetServerAddrDown_fields, &tmp)) {
        if(tmp.has_address)
        {
            server_addr_save(tmp.address.ip, tmp.address.port);
            logfile_write(1,"server set:%s,%d",tmp.address.ip,tmp.address.port);
            simple_event_send(event_change_server, 100);
            return pb_ack_encode(ostream, tlv_ack_ok);
        }
    }
    return 0;
}

/**
 * @brief F5
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static inline uint16_t pb_get_dev_info(pb_istream_t* istream, pb_ostream_t* ostream)
{
    GetDeviceVersionUp tmp;
    memset(&tmp, 0, sizeof(GetDeviceVersionUp));

    LOG_NAME();

    tmp.ack = 1;
    tmp.network_mode = 5;
    char buf[33];
    uint8_t len = snprintf(buf, sizeof(buf), "ml307r-dc sdk1.0.0 sv:%04X",app_get_ver());
    strncpy(tmp.software_ver, buf, len);

    if (pb_encode(ostream, GetDeviceVersionUp_fields, &tmp)) {
        return ostream->bytes_written;
    }
    return 0;
}


/**
 * @brief F6
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static inline uint16_t pb_sys_mode(pb_istream_t* istream, pb_ostream_t* ostream)
{
    MaintenanceModeDown tmp;
    memset(&tmp, 0, sizeof(MaintenanceModeDown));
    uint8_t ack = tlv_ack_format_error;

    LOG_NAME();
        
    if (pb_decode(istream, MaintenanceModeDown_fields, &tmp)) {
        if(tmp.action == 1)
        {
            if(tmp.status < SYS_MODE_MAX)
            {
                user_config.maintenance_mode = tmp.status;
                user_config_save_enable();
                char* mode_name[3]={"patrol", "manual", "maintenance"};
                logfile_write(1,"sys mode change:%s",mode_name[tmp.status]);
                ack = tlv_ack_ok;
            }
        }
        else if(tmp.action == 2)
        {
            ack = tlv_ack_ok;
        }
    }

    MaintenanceModeUp tmp_up;
    memset(&tmp_up, 0, sizeof(MaintenanceModeUp));

    tmp_up.ack = ack;
    tmp_up.action = tmp.action;
    tmp_up.status = user_config.maintenance_mode;

    if(pb_encode(ostream, MaintenanceModeUp_fields, &tmp_up)) {
        return ostream->bytes_written;
    }
    return 0;
}

/**
 * @brief F7
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static inline uint16_t pb_get_timestamp(pb_istream_t* istream, pb_ostream_t* ostream)
{
    SystemTime tmp;
    memset(&tmp, 0, sizeof(SystemTime));

    LOG_NAME();
            

    //tmp.timestamp=cm_rtc_get_current_time();
    tmp.timestamp=RTC_get_timestamp( );   //timestamp


    if(pb_encode(ostream, SystemTime_fields, &tmp)) {
        return ostream->bytes_written;
    }
    return 0;
}

/**
 * @brief F8
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static inline uint16_t pb_set_timestamp(pb_istream_t* istream, pb_ostream_t* ostream)
{
     LOG_NAME();
           
    return pb_ack_encode(ostream, tlv_ack_ok);//ntp获取，不再处理
}


/**
 * @brief F9
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static inline uint16_t pb_get_timezone(pb_istream_t* istream, pb_ostream_t* ostream)
{
    TimeZoneConfig tmp;
    memset(&tmp, 0, sizeof(TimeZoneConfig));

    LOG_NAME();
            

    tmp.longitude = user_config.longitude;
    tmp.latitude = user_config.latitude;
    tmp.time_zone = user_config.time_zone;

    if(pb_encode(ostream, TimeZoneConfig_fields, &tmp)) {
        return ostream->bytes_written;
    }
    return 0;
}

/**
 * @brief FA
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static inline uint16_t pb_set_timezone(pb_istream_t* istream, pb_ostream_t* ostream)
{
    uint8_t ack = tlv_ack_format_error;
    TimeZoneConfig tmp;
    memset(&tmp, 0, sizeof(TimeZoneConfig));

     LOG_NAME();
           

    if(pb_decode(istream, TimeZoneConfig_fields, &tmp))
    {
        if( coord_timezone_check(tmp.latitude,tmp.longitude,tmp.time_zone) )
        {
            coord_timezone_save(tmp.latitude,tmp.longitude,tmp.time_zone) ;
            ack = tlv_ack_ok;
            logfile_write(1,"set timezone:%d,%d,%d",
                          tmp.longitude,tmp.latitude,tmp.time_zone);
        }    
    }
    return pb_ack_encode(ostream, ack);
}


/**
 * @brief FB
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static inline uint16_t pb_plat_dbg(pb_istream_t* istream, pb_ostream_t* ostream)
{
            
    TransmissionSwitchDown tmp;
    memset(&tmp, 0, sizeof(TransmissionSwitchDown));

    LOG_NAME();

    if (pb_decode(istream, TransmissionSwitchDown_fields, &tmp)) {
        if(tmp.action < 2)
        {
            plat_dbg_set(tmp.action);
        }
    }

    TransmissionSwitchUp tmp_up;
    memset(&tmp_up, 0, sizeof(TransmissionSwitchUp));

    tmp_up.ack = tlv_ack_ok;
    tmp_up.action = tmp.action;
    tmp_up.status = plat_dbg_get();
    if (tmp.action < 2 && tmp_up.status != tmp.action) {
        tmp_up.ack = tlv_ack_no;
    }

    if(pb_encode(ostream, TransmissionSwitchUp_fields, &tmp_up)) {
        return ostream->bytes_written;
    }
    return 0;
}

/**
 * @brief FC
 * 
 * @param data 
 * @param len 
 */
static void plat_dbg_down(uint8_t seq, uint8_t* data, uint16_t len, tlv_t* pf_tx)
{
    uint8_t ack = 1,txlen;
    LOG_NAME();

    txlen = tlv_pack(pf_tx, 0xFC, seq, &ack, 1);
    plat_send((uint8_t *)pf_tx, txlen);


    char* cmd = (char*)(data+3);
    uint16_t cmd_len = data[1] | data[2] << 8;  //低字节在前
    if (cmd_len>128 || cmd_len+3>len) return;
    cmd[cmd_len] = 0;

    test_mode_app(cmd, cmd_len, pf_tx, chan_tcp_plat);
}



/***************************************************************************************************** */


/**
 * @brief 10
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static inline uint16_t pb_get_file_list(pb_istream_t* istream, pb_ostream_t* ostream)
{          
    //本设备没有目录，直接上传文件列表
    ReadDirentUp tmp;
    memset(&tmp, 0, sizeof(ReadDirentUp));

     LOG_NAME();

    tmp.ack = 1;

    for(uint8_t i=0;i<LOG_FILE_NUM;i++)
    {
        const char* name = logfile_get_name(i);
        if(cm_fs_exist(name))
        {
            snprintf(tmp.list[tmp.list_count].name, 33, "logfile%d.txt", tmp.list_count);
            tmp.list[tmp.list_count].is_dir = 0;
            tmp.list_count++;
        }
    }

    if(pb_encode(ostream, ReadDirentUp_fields, &tmp)) {
        return ostream->bytes_written;
    }
    return 0;
}

/**
 * @brief 11
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static inline uint16_t pb_get_file_set(pb_istream_t* istream, pb_ostream_t* ostream)
{       
    //本设备没有目录，直接上传文件列表
    StartUploadFileDown tmp;
    StartUploadFileUp tmp_up;

    LOG_NAME();

    memset(&tmp, 0, sizeof(StartUploadFileDown));
    memset(&tmp_up, 0, sizeof(StartUploadFileUp));

    tmp_up.ack = 4;//文件不存在

    if (pb_decode(istream, StartUploadFileDown_fields, &tmp)) {
        int file_num;
        if(sscanf(tmp.file_name,"logfile%d",&file_num) == 1)
        {
            const char* name = logfile_get_name(file_num);
            if(cm_fs_exist(name))
            {
                tmp_up.ack = 1;
                tmp_up.err_code=0;
                tmp_up.path[0] = '/';
                strncpy(tmp_up.file_name, tmp.file_name, 33 - 1);
                
                logfile_upload_set(file_num);
            }
        }
    }

    if(pb_encode(ostream, StartUploadFileUp_fields, &tmp_up)) {
        return ostream->bytes_written;
    }
    return 0;
}


/**
 * @brief 13
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static inline uint16_t pb_download_file_set(pb_istream_t* istream, pb_ostream_t* ostream)
{          
    //本设备没有目录，直接上传文件列表
    StartDownloadFileUp tmp;

    memset(&tmp, 0, sizeof(StartDownloadFileUp));

     LOG_NAME();

    tmp.ack = 0;
    tmp.err_code = 1;//暂不支持此功能

    if(pb_encode(ostream, StartDownloadFileUp_fields, &tmp)) {
        return ostream->bytes_written;
    }
    return 0;
}



/**
 * @brief 14
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static inline uint16_t pb_del_file(pb_istream_t* istream, pb_ostream_t* ostream)
{
    //本设备没有目录，直接上传文件列表
    RemoveFileUp tmp;

    memset(&tmp, 0, sizeof(RemoveFileUp));

     LOG_NAME();


    tmp.ack = 0;
    tmp.err_code = 1;//暂不支持此功能
    strncpy(tmp.err_msg, "Unsupported features",33-1);

    if(pb_encode(ostream, RemoveFileUp_fields, &tmp)) {
        return ostream->bytes_written;
    }
    return 0;
}





/***************************************************************************************************** */


/* 设备物模型定义 ----------------------------------------------------------------------------------------------------------------
typedef struct _SubDevice {
    uint32_t sub_uid;
    uint32_t sub_type;
    uint32_t group_num;
    uint32_t channel;
    uint64_t sub_mac;
    uint32_t link;
    uint64_t link_mac;
} SubDevice;

----------------------------------------------------------------------------------------------------------------*/

// typedef struct{
//     uint16_t sub_uid ;     //id---sub_uid	子设备ID	子设备唯一ID（取值范围：1~65535）{平台自动生成，与设备类型绑定，下发给网关，网关需要保存}
//     uint16_t node_type;    //type sub_type 子设备类型	用于区分不同类型的设备（见附录A）
//     uint8_t  group;        //取值范围：1~255；注意：组号不能为0
    
//     dev_chan_e chan;       //通信方式 /通道号
//     dev_chan_e link_chan;   //转接终端 通信方式

//     uint64_t sub_mac;      //设备通讯地址码
//     uint64_t link_mac;    //转接终端设备地址码

//     //uint8_t addr[6];

// }  dev_topo_t;







/**
 * @brief  60H    下发网关子设备拓扑 60H 
 * 
 * @param pf_rx 
 * @param pf_tx 
 * @return uint16_t 发送数据的长度 
 */

//static uint16_t pb_set_subdev_topo(ptlv_t pf_rx, ptlv_t pf_tx)
static uint16_t pb_set_subdev_topo(pb_istream_t* istream, pb_ostream_t* ostream)
{
    uint8_t num ;
    iot_NodeCfg_Com_u	node_cfg;

#if 0
    uint8_t* pb_rx = pf_rx->data + sizeof(tlv_head_t);
    uint16_t pb_rxlen = pf_rx->body_lenH;
    pb_rxlen = pb_rxlen<< 8 | pf_rx->body_lenL; //debug

    pb_istream_t istream = pb_istream_from_buffer(pb_rx, pb_rxlen);

    uint8_t* pb_tx = pf_tx->data + sizeof(tlv_head_t);

    pb_ostream_t ostream = pb_ostream_from_buffer(pb_tx, TLV_TXPB_SIZE);
#endif



    LOG_NAME();



// /* 下发网关子设备拓扑 60H */
// typedef struct _SendSubDeviceDown {
//     pb_size_t list_count;
//     SubDevice list[32];
//     uint32_t index;
//     uint32_t flag;
// } SendSubDeviceDown;


    SendSubDeviceDown tmp;
    memset(&tmp, 0, sizeof(SendSubDeviceDown) );

    if( pb_decode(istream, SendSubDeviceDown_fields, &tmp) )
     {

     //   tmp.index = 1;// 帧序号	当前发送帧的位置，从1开始
     //   tmp.flag = 0;//后续帧标志	1=后续有数据，0=后续无数据，传输结束 

        num = tmp.list_count;
        printf("add node num=%d\n",num);


        for( uint8_t cnt=0;cnt<num;cnt++ )
        {
            node_cfg.Node.node_type = tmp.list[cnt].sub_type ;
            node_cfg.Node.sub_uid   = tmp.list[cnt].sub_uid ;
            node_cfg.Node.group     = tmp.list[cnt].group_num ;
            node_cfg.Node.channel   = tmp.list[cnt].channel ;
            node_cfg.Node.link_channel = tmp.list[cnt].link ;

            node_cfg.Node.sub_mac  = tmp.list[cnt].sub_mac&0xFF ;
            node_cfg.Node.link_mac = tmp.list[cnt].link_mac ;

           // tmp.list[tmp.list_count].sub_mac = dev_mac_u64(node_cfg->addr);

           iot_Node_Add( &node_cfg )	;

        }

    }


 return pb_ack_encode(ostream, tlv_ack_ok);   //本设备非网关，直接回复ok

}



////7.3.2.2删除网关子设备拓扑 61H

// message ClearSubDeviceDown {
//     uint32 clear_mode=1;
//     uint32 sub_type=2;
//     uint32 group_num=3;
//     uint32 sub_uid=4;
// }
// 编号	字段	名称	说明
// 1	clear_mode	删除方式	0：删除所有设备 1：按类型删除所有设备 2：按sub_uid删除一个设备
// 2	sub_type	子设备类型	读取方式为1时，该值有效. 标识需要删除的设备类型（见附录A）
// 3	group_num	分组号	指定删除的分组，0表示删除所有分组。// 注：删除方式为1时有效
// 4	sub_uid	子设备ID	子设备唯一ID，删除方式为2时有效


/**
 * @brief  61H   ////7.3.2.2删除网关子设备拓扑 61H
 * 
 * @param pf_rx 
 * @param pf_tx 
 * @return uint16_t 发送应答数据的长度 
 */

//static uint16_t pb_del_subdev_topo(ptlv_t pf_rx, ptlv_t pf_tx)

static uint16_t pb_del_subdev_topo(pb_istream_t* istream, pb_ostream_t* ostream)
{
   uint8_t index ;
   uint8_t clear_mode,group ;

   uint8_t ack_code = tlv_ack_ok;

   dev_mod_info_t info;


#if 0
    uint8_t* pb_rx = pf_rx->data + sizeof(tlv_head_t);

    uint16_t pb_rxlen = pf_rx->body_lenH;
    pb_rxlen = pb_rxlen << 8 | pf_rx->body_lenL; //debug

    pb_istream_t istream = pb_istream_from_buffer(pb_rx, pb_rxlen);

    logout("payload decode data:");
    UartPrintHex2String(pb_rx ,pb_rxlen );


    uint8_t* pb_tx = pf_tx->data + sizeof(tlv_head_t);

    pb_ostream_t ostream = pb_ostream_from_buffer(pb_tx, TLV_TXPB_SIZE);

#endif



    LOG_NAME();


// message ClearSubDeviceDown {
//     uint32 clear_mode=1;
//     uint32 sub_type=2;
//     uint32 group_num=3;
//     uint32 sub_uid=4;
// }

    ClearSubDeviceDown tmp;
    memset(&tmp, 0, sizeof(ClearSubDeviceDown) );

    if( true == pb_decode(istream, ClearSubDeviceDown_fields, &tmp) )
     {

// 编号	字段	名称	说明
// 1	clear_mode	删除方式	0：删除所有设备 1：按类型删除所有设备 2：按sub_uid删除一个设备
// 2	sub_type	子设备类型	删除方式为1时，该值有效. 标识需要删除的设备类型（见附录A）
// 3	group_num	分组号	指定删除的分组，0表示删除所有分组。// 注：删除方式为1时有效
// 4	sub_uid	子设备ID	子设备唯一ID，删除方式为2时有效

        logout("clear_mode=%d,sub_type=%d,group_num=%d,sub_uid=%d\n",tmp.clear_mode,tmp.sub_type,tmp.group_num,tmp.sub_uid);

        clear_mode = tmp.clear_mode;
        group      = tmp.group_num;

       if( 0==clear_mode )
       {//0：删除所有设备 
            iot_Node_Clear( );

#if  CJSON_WHITE_LIST_ENABLE             
            json_write_cmd_clear_white_list( );
#endif
        }else if( 1==clear_mode )
       {//1：按类型删除所有设备
          // sub_type = tmp.sub_type;

            if( 0==group )
            {//0：删除所有设备 
              //  iot_Node_Clear( );
                iot_Node_DeleteType( tmp.sub_type );  

#if  CJSON_WHITE_LIST_ENABLE             
                json_write_cmd_clear_white_list( );
#endif

            }else{
            //删除组节点

              //  iot_Node_DeleteType( tmp.sub_type );        ////?????如何区分组删除和类型删除
              //  iot_Node_DeleteGroup( group );

                 //根据节点类型，在指定的组节点
                 iot_Node_DeleteGroup_ByType(group,tmp.sub_type  );

            }

        }else if( 2==clear_mode )
       {//2：按sub_uid删除一个设备
      
           ////返回 0=查找失败， !0= 查找成功
           if( iot_find_node_index_with_sub_uid( tmp.sub_uid ,&index ) )
           {
               iot_get_mod_info_with_index( index , &info);
               
#if  CJSON_WHITE_LIST_ENABLE                 
               json_write_cmd_del_white_list( &info, iot_get_node_type_with_index(index) );
#endif
           }           

            iot_Node_Delete( tmp.sub_uid );

       }else{

            ack_code =  tlv_ack_invalid;     //??? //不支持的功能  48---
            printf("cmd not support\n");
       }

    }

 return pb_ack_encode(ostream, ack_code);  

}






/**
 * @brief  63H   //7.3.2.4保存网关子设备拓扑 63H
 * 将下发的子设备拓扑存入集中器
 * 数 据 域：无
 * @param pf_rx 
 * @param pf_tx 
 * @return uint16_t 发送应答数据的长度 
 */

//static uint16_t pb_save_subdev_topo(ptlv_t pf_rx, ptlv_t pf_tx)

static uint16_t pb_save_subdev_topo(pb_istream_t* istream, pb_ostream_t* ostream)
{

#if  0
    uint8_t* pb_tx = pf_tx->data + sizeof(tlv_head_t);
    pb_ostream_t ostream = pb_ostream_from_buffer(pb_tx, TLV_TXPB_SIZE);
#endif


    LOG_NAME();

///数 据 域：无
	iot_Node_ParamSaveFlashEnable( ); 

 return pb_ack_encode(ostream, tlv_ack_ok ); //ack_code 

}








#define MAX_NUM_IN_PACKET (32u)

/**
 * @brief 62
 *    ////重新计数------每次只发送 MAX_NUM_IN_PACKET 个节点的数据-------
     ////当前是连包发送，没有分帧发送处理，
 * @param pf_rx 
 * @param pf_tx 
 */


static void pb_get_subdev_topo(ptlv_t pf_rx, ptlv_t pf_tx)
{
    uint8_t num ,count;  
    iot_NodeConfig_st*  node_cfg =NULL;

//#if 1
    tlv_head_t* phead_rx = (tlv_head_t*)pf_rx->data;
    uint8_t* pb_rx = pf_rx->data + sizeof(tlv_head_t);

    uint16_t pb_rxlen = pf_rx->body_lenH;
    pb_rxlen = pb_rxlen << 8 | pf_rx->body_lenL; 

    pb_istream_t istream = pb_istream_from_buffer(pb_rx, pb_rxlen);

    uint8_t* pb_tx = pf_tx->data + sizeof(tlv_head_t);
    pb_ostream_t ostream = pb_ostream_from_buffer(pb_tx, TLV_TXPB_SIZE);

//#endif

     LOG_NAME();


    ReadSubDeviceDown tmp;
    memset(&tmp, 0, sizeof(ReadSubDeviceDown));

    if(pb_decode(&istream, ReadSubDeviceDown_fields, &tmp))
    {
        //tmp.read_mode
        //tmp.sub_type
        //tmp.group_num

 //      num = devm_get_num();
         num = iot_Node_GetNum();

        ReadSubDeviceUp tmp_up;
        memset(&tmp_up, 0, sizeof(ReadSubDeviceUp));

        if(num == 0)
        {
            tmp_up.flag = 0;
            tmp_up.index = 1;
            tmp_up.list_count = 0;


 //           logout("payload decode data:");
 //           UartPrintHex2String(pb_rx ,pb_rxlen );

            if(pb_encode(&ostream, ReadSubDeviceUp_fields, &tmp_up))
            {
                                                                //应答的seq等于接收到的seq  ---phead_rx->seq
                uint16_t txlen = tlv_seal(pf_tx, phead_rx->cmd, phead_rx->seq, ostream.bytes_written);
                plat_send((uint8_t*)pf_tx, txlen);
            }
            return;
        }


        count = 0;

        for(uint8_t i=0;i<num;i++)
        {
            tmp_up.index++;//index 从1开始

            //dev_topo_t* topoinfo = devm_get_topoinfo(i);
            node_cfg = iot_Node_Get_topoinfo( i);

            if( NULL==node_cfg )
            {
                return;
                //break;
            }


            tmp_up.list[count].sub_uid   = node_cfg->sub_uid;
            tmp_up.list[count].sub_type  = node_cfg->node_type;
            tmp_up.list[count].group_num = node_cfg->group;
            tmp_up.list[count].channel   = node_cfg->channel;

            tmp_up.list[count].link     = node_cfg->link_channel ;
            tmp_up.list[count].sub_mac  = node_cfg->sub_mac ;
            tmp_up.list[count].link_mac = node_cfg->link_mac ;

           // tmp_up.list[count].sub_mac = dev_mac_u64(node_cfg->addr);

            count++;

            if( (count >= MAX_NUM_IN_PACKET) || (i==num-1) )
            {   ///----------每次只发送 MAX_NUM_IN_PACKET 个节点的数据-------

                tmp_up.flag = (i < num-1)?1:0;//1=有后续数据
                tmp_up.list_count = count;

                if(pb_encode(&ostream, ReadSubDeviceUp_fields, &tmp_up))
                {
                                                                          //应答的seq等于接收到的seq  ---phead_rx->seq
                    uint16_t txlen = tlv_seal(pf_tx, phead_rx->cmd, phead_rx->seq, ostream.bytes_written);
                    plat_send((uint8_t*)pf_tx, txlen);
                }

                count = 0;
                tmp_up.list_count = 0;  ////重新计数------每次只发送 MAX_NUM_IN_PACKET 个节点的数据-------
                ////当前是连包发送，没有分帧发送处理，
            }
        }
    }

    
}






/**
 * @brief 
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t  返回发送数据的长度
 */
static inline uint16_t pb_get_dev_attr(pb_istream_t* istream, pb_ostream_t* ostream)
{
uint8_t index;

// char iot_ids_str[8];   //

dev_mod_info_t   dev_mod_info;

     LOG_NAME();

    GetSubIotDataDown tmp;
    memset(&tmp, 0, sizeof(GetSubIotDataDown));

    GetSubIotDataUp updata;
    memset(&updata, 0, sizeof(GetSubIotDataUp));

    if(pb_decode(istream, GetSubIotDataDown_fields, &tmp)){
        
        updata.ack = 1;
        updata.has_data = 1;
        //updata.data.sub_uid = 0;  // bug 
        updata.data.sub_uid = tmp.sub_uid;  //debug ---dan---
       // updata.data.state = OnlineState_ONLINE;

         //返回 0=查找失败， !0= 查找成功
         if( iot_find_node_index_with_sub_uid( tmp.sub_uid, &index ) )
         {
             updata.data.state = iot_get_device_online_state_with_index(index);   
         }else{
             updata.data.state = OnlineState_OFFLINE ;  
             //设备不存在 
             return 0;
         }


       //手动读属性的时候，需要实时读取，不能读取缓存数据
            //"nodeType":1,"channel":2,"privAddr":1,
            //  dev_mod_info.node_type = 1;////这里与上行协议不一样---json协议对应的是485固定为1----
            //  dev_mod_info.chan_addr = 1;
            //  dev_mod_info.channel = 2;

          iot_get_mod_info_with_index( index , &dev_mod_info );


//(	"mask",	"01");
//(	"classMask","11");
// 0：读取指定测量点
// 1：读取设备运行参数
// 2：读取设备配置参数
// 3：读取设备出厂信息


        if( 0 ==tmp.read_type) 
        {// 0：读取指定测量点
            logout( "iot_id_count=%d\n",tmp.iot_ids_count );
            for(uint8_t i=0;i<tmp.iot_ids_count ;i++)
            {
                logout( "iot_ids[%d]=%d\n",i,tmp.iot_ids[i] );
            }
              
            if(tmp.iot_ids_count)  
            {
               json_readAttr_single_num_by_dpid(dev_mod_info.chan_addr , dev_mod_info.channel,1,  tmp.iot_ids_count, tmp.iot_ids ); 
            }else{
               logout( "iot_id_count error\n");
            }


        } else {     

            if( IOT_SENSOR_smoke ==dev_mod_info.node_type )
            {
                 json_readAttr_single_with_mask(dev_mod_info.chan_addr , dev_mod_info.channel,1,NULL,NULL ); 
                //这里与上行协议不一样---json协议对应的是485固定为1----            
            }else{
                json_readAttr_single_with_mask(dev_mod_info.chan_addr , dev_mod_info.channel,1 ,"01","11");    
                // json_readAttr_single(dev_mod_info.chan_addr , dev_mod_info.channel,1 ,"01","11");  
                //这里与上行协议不一样---json协议对应的是485固定为1----
            }

        }



#if 0    ////离线状态的数据--------
        if( OnlineState_OFFLINE == updata.data.state )
        {
            updata.data.props_count = 0;    //离线不需要发送props字段
            
            if(pb_encode(ostream, GetSubIotDataUp_fields, &updata)){
                return ostream->bytes_written;
            }else{
                return 0;
            }
        } 

            if( 0==mode )
            { ///上电--首次上报数据----- //上报所有参数   
                tmp_up.list[node_cnt].props_count =  dev_module_read_attr( node_index, dev_read_all,  tmp_up.list[node_cnt].props ) ;       //上报所有参数     
            }else{
                 //只上报重要的运行参数

                if( OnlineState_ONLINE == tmp_up.list[node_cnt].state )
                { //////在线才需要上报运行数据--------
                //  tmp_up.list[node_cnt].props_count =  dev_module_read_attr( sub_uid, dev_read_run_data,  tmp_up.list[node_cnt].props ) ;   //只上报运行数据
                    tmp_up.list[node_cnt].props_count =  dev_module_get_attr_run_data( node_index, tmp_up.list[node_cnt].props ) ;   //只上报运行数据
                }else{//离线

#endif

        // sleep(1);  // 2 sec----等待传感器设备应答-----
        usleep(TLV_Write_wait_us);   ////经测试500ms刚好合适，100ms太快，1sec太慢
        

       //sleep(2);  // 2 sec----等待传感器设备应答-----
        printf("65H_ack:sub_uid=%d,read_type=%d,node_type=%x,index=%d\n",tmp.sub_uid,tmp.read_type,dev_mod_info.node_type,index );

        if( tmp.read_type != 0) {//按类型读取
//            updata.data.props_count =  dev_module_read_attr( tmp.sub_uid, tmp.read_type, &updata.data.props[0] ) ;
             updata.data.props_count =  dev_module_read_attr( index, tmp.read_type, &updata.data.props[0] ) ;     //测试ok 
            // updata.data.props_count =  dev_module_get_attr_run_data( index, &updata.data.props[0] ) ;      //test ok

            if (pb_encode(ostream, GetSubIotDataUp_fields, &updata) ) {
                return ostream->bytes_written;
            }      

        }else{//读取指定测量点

            for(int i=0;i<tmp.iot_ids_count;i++){
              // dev_module_get_attr(tmp.sub_uid,tmp.iot_ids[i], &updata.data.props[i]);
               // dev_module_get_attr(index,tmp.iot_ids[i], &updata.data.props[i]);
                dev_module_65H_read_type_0_get(i,tmp.iot_ids[i], &updata.data.props[i]);            
            }


            updata.data.props_count = tmp.iot_ids_count;
            
            if( pb_encode(ostream, GetSubIotDataUp_fields, &updata) ) {
                return ostream->bytes_written;
            }
        }
    }

    return 0;
}




















/**
 * @brief 
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static uint16_t pb_set_dev_attr(pb_istream_t* istream, pb_ostream_t* ostream ,uint8_t seq)
{
    LOG_NAME();

    uint8_t index;
    dev_mod_info_t info ;

    SetSubIotData tmp;
    memset(&tmp, 0, sizeof(SetSubIotData)) ;

    int ack = tlv_ack_ok;

    if( true ==pb_decode(istream, SetSubIotData_fields, &tmp) ) 
    {
        //tmp.c_type        //本设备只能单点控制
        //tmp.group_num     //本设备只能单点控制
        //tmp.sub_uid       //本设备只能单点控制
        
// 1	c_type	控制类型	见枚举定义 1：单点控制，2：分组控制，3：广播控制
// 2	group_num	分组号	分组号（从1开始，分组控制有效）
// 3	sub_uid	子设备ID	子设备唯一ID（单点控制有效）

// 响应结果	1：集中器成功受理。 
// 2：命令或数据格式错误。
// 3：集中器忙

       if( tlv_ctr_point != tmp.c_type ) 
       {//当前只支持单点控制
            logout("pb c_type =%d error\n",tmp.c_type );
            return pb_ack_encode(ostream, tlv_ack_invalid );  //不支持的功能  48---
       }


#if  0    ///-------------一次性写完所有配置参数------------------------

        //属性写入
        for(int i = 0; i < tmp.props_count; i++){
            if( dev_module_set_attr(tmp.sub_uid,  &tmp.props[i]) == 0) {
                ack = tlv_ack_format_error;
                break;
            }
        }


        if( iot_find_node_index_with_sub_uid( tmp.sub_uid, &index ) )
        {

                if( iot_get_mod_info_with_index( index , &info) )
                {
                    //"deviceName": "oilLevel_2_1",
                   // json_write_setAttr_dev_config(index, &info,"oilLevel_2_1");

                    json_write_setAttr_dev_config(index, &info );
                    json_write_setAttr_dev_config_one(index, &info );

                }
        }

#else

/////////----------///一次只写一个配置参数-------------------------------------------

        if( iot_find_node_index_with_sub_uid( tmp.sub_uid, &index ) )
        {
            if( iot_get_mod_info_with_index( index , &info) )
            {

                //属性写入
                for(int i = 0; i < tmp.props_count; i++)
                {
                    // if( dev_module_set_attr(tmp.sub_uid,  &tmp.props[i]) == 0)    //效率低
                    if( dev_module_set_attr_with_index( index,&tmp.props[i]) == 0)   //效率高
                    {
                        ack = tlv_ack_format_error;
                        break;
                    }

                    if(tmp.props_count<=TLV_Write_iot_id_num_max)
                    { //一次只写一个iot_id对应的配置参数-------json 协议参数用的是 dpid------
                        json_write_setAttr_dev_config_one_with_pdip(index, seq,&info,&tmp.props[i] );  
                    }

                }
        
                if(tmp.props_count>TLV_Write_iot_id_num_max)
                {    // 一次写设备所有R/W的配置参数-------json 协议参数用的是name------
                    json_write_setAttr_dev_config_all_with_name(index,seq, &info ); 
                    //json_write_setAttr_dev_config_all_with_pdip(index, &info);  
                }

                ack = tlv_ack_ok;    

            }else{
                ack = tlv_ack_format_error;   
            }

        }else{
            ack = tlv_ack_format_error;   
        }

#endif 

    }else{//pb解码失败
        ack = tlv_ack_pb_decode_error;//???? 49-----pb解码失败
        printf("pb_decode false\n");
    }

    if( tlv_ack_ok != ack ) 
    {//出错需立即应答
        return pb_ack_encode(ostream, ack);    
    }else{ //需要等待节点的应答
        return 0;    
    }

}


static void pb_get_subdev_topo_00(ptlv_t pf_rx, ptlv_t pf_tx)
{
 
    uint8_t* pb_tx = pf_tx->data + sizeof(tlv_head_t);
    pb_ostream_t ostream = pb_ostream_from_buffer(pb_tx, TLV_TXPB_SIZE);


    // tmp_up.flag = (i < num-1)?1:0;//1=有后续数据
    // tmp_up.list_count = count;

    // if(pb_encode(&ostream, ReadSubDeviceUp_fields, &tmp_up))
    // {
    //                                                             //应答的seq等于接收到的seq  ---phead_rx->seq
    //     uint16_t txlen = tlv_seal(pf_tx, phead_rx->cmd, phead_rx->seq, ostream.bytes_written);
    //     plat_send((uint8_t*)pf_tx, txlen);
    // }


    
}






// message IotModel {
//     uint32 iot_id=1;
//     uint64 value=2;
//     bytes values=3;
// }


// message SubIotData {
//     uint32 sub_uid=1;
//     OnlineState state=2;
//     repeated IotModel props=3;
// }

// 上报子设备属性 67H
// message ReportSubIotData {
//     repeated SubIotData list=1;
// }



// typedef PB_BYTES_ARRAY_T(33) IotModel_values_t;
// typedef struct _IotModel {
//     uint32_t iot_id;
//     uint64_t value;
//     IotModel_values_t values;
// } IotModel;

// typedef struct _SubIotData {
//     uint32_t sub_uid;
//     OnlineState state;
//     pb_size_t props_count;
//     IotModel props[32];
// } SubIotData;


// /* 上报子设备属性 67H */
// typedef struct _ReportSubIotData {
//     pb_size_t list_count;
//     SubIotData list[32];
// } ReportSubIotData;


/////7.3.3.3 主动上报子设备属性 67H 命令每次上传的最大节点数
//#define AUTO_REPORT_MAX_NODE_NUM_PER        (16u)  ///?????
#define AUTO_REPORT_MAX_NODE_NUM_PER        (32u)  ///?????



/**
 * @brief mode=0,只上报重要的运行参数，mode=1上报所有运行参数
 *        -->平台不好处理，任何时候都上报全部参数
 * @param mode 
 */
static void pb_report_dev_attr(uint8_t mode)
{
    uint8_t num ,node_cnt;  
    uint16_t sub_uid ;
    uint16_t tmpu16;

 // ReportSubIotData tmp_up; 
    //IotModel attr_out;


    uint8_t  txbuf[TLV_TXBUF_SIZE];

    tlv_t*   pf_tx = (tlv_t*)txbuf;
    uint8_t* pb_tx = pf_tx->data + sizeof(tlv_head_t);
    pb_ostream_t ostream = pb_ostream_from_buffer(pb_tx, TLV_TXPB_SIZE);

    num = iot_Node_GetNum();

    if(num == 0)
    {////白名单是空的，就不主动上报了
        printf("whitelist is null,no report\n");
        return;
    }


    LOG_NAME();

       ReportSubIotData tmp_up; 
       memset((uint8_t*)&tmp_up, 0, sizeof(tmp_up) );

       tmp_up.index = 1;   // 帧序号	当前发送帧的位置，从1开始
       tmp_up.flag  = 0;   // 后续帧标志	1=后续有数据，0=后续无数据，传输结束

        node_cnt = 0;

       //    dev_module_get_run_data_len( index )  ;
       //    dev_module_get_config_data_len(index ) ; 
    


        for(uint8_t node_index =0; node_index<num;node_index++)
        {

            ////props	设备属性列表---全称为properties，缩写为props。格式见上文（设备离线时该值填NULL）

             // OnlineState_ONLINE;  //返回0=表示离线，0!=表示在线======离线不需要上传数据============
 //         tmp_up.list[node_cnt].state  = OnlineState_ONLINE;  //iot_get_device_online_state_with_node_index( node_index ); 
            tmp_up.list[node_cnt].state  = iot_get_device_online_state_with_index( node_index ); 


            if( 0==mode )
            { ///上电--首次上报数据----- //上报所有参数   
                tmp_up.list[node_cnt].props_count =  dev_module_read_attr( node_index, dev_read_all,  tmp_up.list[node_cnt].props ) ;       //上报所有参数     
            }else{
                 //只上报重要的运行参数

                if( OnlineState_ONLINE == tmp_up.list[node_cnt].state )
                { //////在线才需要上报运行数据--------
                //  tmp_up.list[node_cnt].props_count =  dev_module_read_attr( sub_uid, dev_read_run_data,  tmp_up.list[node_cnt].props ) ;   //只上报运行数据
                    tmp_up.list[node_cnt].props_count =  dev_module_get_attr_run_data( node_index, tmp_up.list[node_cnt].props ) ;   //只上报运行数据
                }else{//离线
                    tmp_up.list[node_cnt].props_count = 0;   ////（设备离线时该值填NULL）
                }

            }


            iot_get_node_sub_uid_with_index( node_index, &sub_uid );
            tmp_up.list[node_cnt].sub_uid = sub_uid ;

            node_cnt++;


            if( (node_cnt >= AUTO_REPORT_MAX_NODE_NUM_PER) || (node_index==num-1) )
            {   ///----------每次只发送 MAX_NUM_IN_PACKET 节点的个数---还与属性节点的参数量有关-------

              //  tmp_up.flag = (i < num-1)?1:0;//1=有后续数据
                tmp_up.list_count = node_cnt;

                if( pb_encode(&ostream, ReportSubIotData_fields, &tmp_up) )
                {
                                                         //tlv_get_seq()
                    tmpu16 = tlv_seal(pf_tx, 0x67, seq++, ostream.bytes_written);  //tlv_get_seq()
                    plat_send((uint8_t*)pf_tx, tmpu16);
                }

                node_cnt = 0;
                tmp_up.list_count = 0;  ////重新计数------每次只发送 AUTO_REPORT_MAX_NODE_NUM_PER 个节点的数据-------
                ////当前是连包发送，没有分帧发送处理，
            }

        }

}




/**
 * @brief 主动上报设备属性
 * 
 */
void plat_report_attr_process(void)
{////这个导致上电的第1sec内多次频繁发送上传数据

}


void plat_report_attr_tick_sec(void)
{ 
    static uint32_t last_report_time = 0;
   // static uint8_t status[2],lightness[2];   /////还需要增加状态的记忆保存

   static uint8_t mode = 0;

    if( !hplat_comm.login )
    {
        return;
    }


    // if( 0== iotv30_IsTcpClientConnected( )  )
    // {
    //     return ;
    // }


    //状态不同或者告警变化，立即上报
    // if( status[0] != Pstate_A.state || status[1] != Pstate_B.state || 
    //     lightness[0] != LED_info.ucLED_Bright_A || lightness[1] != LED_info.ucLED_Bright_B){//Pstate_A.state 包括开关跟报警
    //     mode = 0;
    //     status[0] = Pstate_A.state;
    //     status[1] = Pstate_B.state;
    //     lightness[0] = LED_info.ucLED_Bright_A;
    //     lightness[1] = LED_info.ucLED_Bright_B;
    // }

////还需要上电的时候主动上报一次节点信息(不需要周期上报)
 
    if( last_report_time == 0 || (get_runtime_sec() - last_report_time >= TLV_ReportPeriod_sec ) )   /////测试1min上报一次  10*60
    {//上电报一次，10分钟报一次
      
        LOG_NAME();
        pb_report_dev_attr(mode);
        last_report_time = get_runtime_sec();

        if( 0==mode)
        {
            mode = 1;
        }

  }



}



/***************************************************************************************************** */
static bool add_time_task_cb(pb_istream_t *stream, const pb_field_t *field, void **arg)
{
    CronJob tmp={0};
    SetCronJobUp* parg = *arg;

    if(pb_decode(stream, CronJob_fields, &tmp))
    {
        /*定时结构*/
        time_task_t task={0};
        task.task_id = tmp.job_id;
        task.begin_time = tmp.begin_time;
        task.end_time = tmp.end_time;
        task.schedule_mode = (schedule_mode_e)tmp.action_schema;
        task.schedule_weekday = tmp.action_week;
        task.time_mode = (time_mode_e)tmp.computer_mode;
        task.offset = (int8_t)tmp.offset_time;
        task.hour = (uint8_t)(tmp.execute_time & 0xFF);
        task.minute = (uint8_t)((tmp.execute_time >> 8)&0xFF);

        if(tmp.has_job_param == false){
            printf_log(lv1, "time task has no param!\n");
            return false;//必须有任务参数
        }
        
        pb_ostream_t ostream = pb_ostream_from_buffer(task.param, TIME_TASK_PARAM_MAX);
        
        if(pb_encode(&ostream, SetSubIotData_fields, &tmp.job_param))
        {
            task.param_len = ostream.bytes_written;

            parg->job_ids[parg->job_ids_count++]=tmp.job_id;
            printf_log(lv1, "add time task: %d\n", tmp.job_id);
            return time_task_add(&task) ? true : false;
        }//可能数据太长，缓冲区不够
    }
    return false;
}


/**
 * @brief 90
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static uint16_t pb_add_time_task(pb_istream_t* istream, pb_ostream_t* ostream)
{
    SetCronJobDown tmp={0};
    SetCronJobUp tmp_up={0};

    tmp.list.funcs.decode = add_time_task_cb;
    tmp.list.arg = &tmp_up;

    if(pb_decode(istream, SetCronJobDown_fields, &tmp)) {
        tmp_up.ack = tlv_ack_ok;
    }else{
        tmp_up.ack = tlv_ack_format_error;
    }

    if(pb_encode(ostream, SetCronJobUp_fields, &tmp_up)) {
        time_task_save_enable();
        return ostream->bytes_written;
    }
    return 0;
}


static bool get_time_task_cb(pb_ostream_t *stream, const pb_field_t *field, void * const *arg)
{
    void **p = *arg;

    uint32_t* pcount = (uint32_t*)(p[0]);
    GetCronJobUp* pCronJobup = (GetCronJobUp*)(p[1]);

    CronJob tmp={0};

    uint32_t task_num = time_task_get_num();
    for(; *pcount < task_num; (*pcount)++)
    {
        time_task_t* ptask = time_task_get_task(*pcount);
        tmp.job_id = ptask->task_id;
        tmp.begin_time = ptask->begin_time;
        tmp.end_time = ptask->end_time;
        tmp.action_schema = (uint32_t)ptask->schedule_mode;
        tmp.action_week = ptask->schedule_weekday;
        tmp.computer_mode = (uint32_t)ptask->time_mode;
        tmp.execute_time = (uint32_t)(ptask->hour | (ptask->minute << 8));
        tmp.offset_time = (int32_t)ptask->offset;
        tmp.has_job_param = true;

        pb_istream_t istream = pb_istream_from_buffer(ptask->param, ptask->param_len);

        if(!pb_decode(&istream, SetSubIotData_fields, &tmp.job_param))      
        {
            return false;
        }  

        if(!pb_encode_tag_for_field(stream, field))
        {
            printf_log(lv2,"[cmd92]pb encode tag err\n");
            return false;
        }
        if(!pb_encode_submessage(stream, CronJob_fields, &tmp)) {
            printf_log(lv2,"[cmd92]pb encode submsg err\n");
            return false;
        }
        if(stream->bytes_written > TLV_TXPB_SIZE - 200)
        {
            break;
        }
    }

    pCronJobup->index++;
    pCronJobup->flag = (*pcount == task_num) ? 0 : 1;

    return true;
} 

/**
 * @brief 91
 * 
 * @param pf_rx 
 * @param pf_tx 
 */
static void pb_get_time_task(ptlv_t pf_rx, ptlv_t pf_tx)
{

     LOG_NAME();

    tlv_head_t* phead_rx = (tlv_head_t*)pf_rx->data;

    uint8_t* pb_tx = pf_tx->data + sizeof(tlv_head_t);
    pb_ostream_t ostream = pb_ostream_from_buffer(pb_tx, TLV_TXPB_SIZE);

    GetCronJobUp tmp={0};
    uint32_t count = 0;
    void * param[2]={&count,&tmp};

    tmp.list.funcs.encode = get_time_task_cb;
    tmp.list.arg = param;

    for(;count < time_task_get_num();)
    {
        if(pb_encode(&ostream, GetCronJobUp_fields, &tmp))
        {
            uint16_t txlen = tlv_seal(pf_tx, phead_rx->cmd, phead_rx->seq, ostream.bytes_written);
            plat_send((uint8_t*)pf_tx, txlen);
        }
        else
        {
            printf_log(lv2,"[cmd91]pb encode err\n");
            break;
        }
    }
}

/**
 * @brief 
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static uint16_t pb_del_time_task(pb_istream_t* istream, pb_ostream_t* ostream)
{
    ClearCronJobDown tmp={0};
    uint8_t ack = tlv_ack_format_error;

    if(pb_decode(istream, ClearCronJobDown_fields, &tmp)) {
        if(tmp.clear == true)
        {
            time_task_del_all();
            time_task_save_enable();
        }else{
            for(uint32_t i = 0; i < tmp.job_ids_count; i++)
            {
                time_task_del(tmp.job_ids[i]);
            }
            time_task_save_enable();
        }
        ack = tlv_ack_ok;
    }
    return pb_ack_encode(ostream, ack);
}

/**
 * @brief 93 主动上报定时任务执行任务结果
 * 
 * @param pf_tx 
 * @param task_num 
 * @param error 0=ok,其它=error code 
 */
uint16_t pb_report_time_task(ptlv_t pf_tx, uint8_t task_num, uint8_t error)
{
    LOG_NAME();

    uint8_t* pb_tx = pf_tx->data + sizeof(tlv_head_t);
    pb_ostream_t ostream = pb_ostream_from_buffer(pb_tx, TLV_TXPB_SIZE);

    ExecCronJobUp tmp={0};

    tmp.err_code = error;
    tmp.job_id = task_num;

    if(pb_encode(&ostream, ExecCronJobUp_fields, &tmp)){
        return tlv_seal(pf_tx, 0x93, tlv_get_seq(), ostream.bytes_written);
    }
    return 0;
}


/***************************************************************************************************** */

ota_handle_t hota;

/**
 * @brief C0
 * 
 * @param istream 
 * @param ostream 
 * @return uint16_t 
 */
static uint16_t pb_ota_get_space(pb_istream_t* istream, pb_ostream_t* ostream)
{
    LOG_NAME();

    GetOtaDiskSpaceUp tmp={0};

    tmp.ack = tlv_ack_ok;
    tmp.space = Flash_GetAppSize( ); //cm_ota_get_capacity();
    tmp.support = OTA_C0_MODE_SELECT;  //选择升级模式

    if(pb_encode(ostream, GetOtaDiskSpaceUp_fields, &tmp)){
        return ostream->bytes_written;
    }
    return 0;
}

/**
 * @brief C1
 * 
 * @param rx 
 * @param tx 
 * @return uint16_t 返回内容长度
 */
static uint16_t ota_file_info(uint8_t* rx, uint8_t* tx)
{

    hota.firmware_size = rx[0] | rx[1] << 8 | rx[2] << 16 | rx[3] << 24;
    hota.firmware_crc32 = rx[4] | rx[5] << 8 | rx[6] << 16 | rx[7] << 24;

    hota.firmware_type = rx[8];
    memcpy(hota.node_id, &rx[9], 6);
    hota.unit_type = rx[15] | rx[16] << 8;
    hota.radio = rx[17];
    strncpy((char*)hota.firmware_version, (char*)&rx[18], 31);
    hota.firmware_version[31] = '\0';
    
    cm_ota_init();//

    int avaliable_size = cm_ota_get_capacity();

    if( avaliable_size < hota.firmware_size ){
        tx[0] = tlv_ack_format_error;
        printf_log(lv1, "ota size error. size=%d, avaliable=%d\n", hota.firmware_size, avaliable_size);
    }else if((hota.unit_type & 0xFF00) != (CTK_PID & 0xFF00)){//type  高字节必须相同
        tx[0] = tlv_ack_format_error;
        printf_log(lv1, "ota type error. type=%d\n", hota.unit_type);
    }else{
        tx[0] = tlv_ack_ok;
        printf_log(lv1, "ota: firmware size %d\n", hota.firmware_size);
    }   
    cm_ota_set_otasize(hota.firmware_size);
    return 1;
}






//平台通知设备即将开始固件传输 C1H
static void Set_OTA_Start(uint8_t* prx_data, uint8_t* ptx_data )	
{

	uint8_t   cnt;		
    little_u32_u  tmp32;	

     cnt = 0;

     //小端格式
	 tmp32.u8[0]  = prx_data[cnt++];
	 tmp32.u8[1]  = prx_data[cnt++];
	 tmp32.u8[2]  = prx_data[cnt++];
	 tmp32.u8[3]  = prx_data[cnt++];
    hota.firmware_size  = tmp32.u32;

	 tmp32.u8[0]  = prx_data[cnt++];
	 tmp32.u8[1]  = prx_data[cnt++];
	 tmp32.u8[2]  = prx_data[cnt++];
	 tmp32.u8[3]  = prx_data[cnt++];
    hota.firmware_crc32 = tmp32.u32;

    hota.firmware_type = prx_data[cnt++];
    memcpy(hota.node_id, &prx_data[cnt], 6);    
    cnt += 6;


	 tmp32.u8[0]  = prx_data[cnt++];
	 tmp32.u8[1]  = prx_data[cnt++];

    hota.unit_type = tmp32.u32&0x0000FFFF;

    ////是否广播	true：表示对集中器下所有同unit_type的节点升级其Unit固件
    hota.radio = prx_data[cnt++];

    strncpy((char*)hota.firmware_version, (char*)&prx_data[cnt], sizeof(hota.firmware_version) );
    hota.firmware_version[31] = '\0';
    

    int avaliable_size = Flash_GetAppSize();//cm_ota_get_capacity();

    if( avaliable_size < hota.firmware_size ){
        ptx_data[0] = tlv_ack_format_error;
        printf_log(lv1, "ota size error. size=%d, avaliable=%d\n", hota.firmware_size, avaliable_size);
        logout( "ota size error. size=%d, avaliable=%d\n", hota.firmware_size, avaliable_size);

    // }else if((hota.unit_type & 0xFF00) != (CTK_PID & 0xFF00)){//type  高字节必须相同
    //     ptx_data[0] = tlv_ack_format_error;
    //     printf_log(lv1, "ota type error. type=%d\n", hota.unit_type);


    }else{
        ptx_data[0] = tlv_ack_ok;
        printf_log(lv1, "ota: firmware size %d\n", hota.firmware_size);
        logout( "ota: firmware size %d\n", hota.firmware_size);
    }   


    hota.flash_addr = 0;
   // cm_ota_set_otasize(hota.firmware_size );

#if 0
	//------擦除falsh-------    创建升级文件--------
	//----创建升级文件--------

     ChargeUpdateInit(  );
    if( UpdateFlash_Create( hota.firmware_size) ) 
    {
       logout( "ota Create ok\n");
    }else{
       logout( "ota Create fail\n");
    }
#else
    logout( "ota Create fail\n");
#endif

	 
}






/**
 * @brief C2
 * 
 * @param rx 
 * @param tx 
 * @return uint16_t 返回内容长度
 */
static uint16_t ota_file_recv(uint8_t* rx, uint16_t rxlen, uint8_t* tx)
{
    uint8_t ack;
    uint16_t tmp_total = rx[0] | rx[1] << 8;
    uint16_t tmp_index = rx[2] | rx[3] << 8;

    if(tmp_index == 1)
    {//发第一包时设置总包数和期待包
        hota.total = tmp_total;
        hota.expect = 1;
    }

    if(tmp_index == hota.expect){
        cm_ota_firmware_write((char*)&rx[4], rxlen - 4);
        ack = tlv_ack_ok;
        if(hota.expect < hota.total)hota.expect++;
        printf_log(lv1, "ota: %3d/%3d len=%d\n",tmp_index, tmp_total, rxlen - 4);

    }else{
        printf_log(lv1, "ota: packet error: expect %d, but %d received\n",hota.expect, tmp_index);

        ack = tlv_ack_format_error;
    }
    tx[0] = ack;
    return 1;
}


// uint16_t ota_file_recv(uint8_t* rx, uint16_t rxlen, uint8_t* tx)




//平台通知设备即将开始固件传输 C1H
 static void WriteOTA_Data(uint8_t* prx_data,  uint16_t rxlen,uint8_t* ptx_data )	
{

	uint8_t   cnt;		
    little_u32_u  tmp32;	

    uint8_t ack;
    uint16_t tmp_total ;
    uint16_t tmp_index ;


     cnt = 0;
     //小端格式
	 tmp32.u8[0]  = prx_data[cnt++];
	 tmp32.u8[1]  = prx_data[cnt++];
     tmp_total    = tmp32.u32&0x0000FFFF;

	 tmp32.u8[0]  = prx_data[cnt++];
	 tmp32.u8[1]  = prx_data[cnt++];
     tmp_index    = tmp32.u32&0x0000FFFF;


    if(tmp_index == 1)
    {//发第一包时设置总包数和期待包
        hota.total = tmp_total;
        hota.expect = 1;
    }

    if( tmp_index == hota.expect )
    {
        if(hota.expect < hota.total)
        {//新的数据包
           hota.expect++;

             //返回0--写失败，1--写成功
  //          if( Write_OTA_Flash(&prx_data[4], hota.flash_addr,rxlen - 4 ) )  
            if( cm_ota_firmware_write(&prx_data[4], rxlen - 4 ) )             
            {
              hota.flash_addr += (rxlen - 4);
            }


        }else{ //重复发送

        }


        ack = tlv_ack_ok;
        printf_log(lv1, "ota: %3d/%3d len=%d\n",tmp_index, tmp_total, rxlen - 4);
        logout( "ota: %3d/%3d len=%d\n",tmp_index, tmp_total, rxlen - 4);

    }else{
        ack = tlv_ack_format_error;
        printf_log(lv1, "ota: packet error: expect %d, but %d received\n",hota.expect, tmp_index);
        logout("ota: packet error: expect %d, but %d received\n",hota.expect, tmp_index);     
    }

    ptx_data[0] = ack;

}





/**
 * @brief C3
 * 
 * @param rx 
 * @param tx 
 * @return uint16_t 返回内容长度
 */
static uint16_t ota_file_finish(uint8_t* rx, uint8_t* tx)
{

    if(hota.total == hota.expect)
    {////还需要校验 ----固件文件CRC32校验码------
        tx[0] = tlv_ack_ok;
         //// 在这里通知升级进程
        //simple_event_send(event_ota, 200);
    }else{
        tx[0] = tlv_ack_format_error;
    }
    return 1;
}


//平台查询子节点升级进度C4H



  //  StartOtaGetFirmwareDown ota_http_obj;



/**
 * @brief 设备主动上报OTA升级进度 C7H  应答接收
 * 
 * @param istream 
 * @param ostream 
  * @param uint16_t ret 返回需要应答服务器的数据长度，此处不需要应答，固定返回0
 */
static inline uint16_t pb_ota_http_process_report_rx(pb_istream_t* istream, pb_ostream_t* ostream)
{
    LOG_NAME();

   //ack	响应结果	1：平台接收成功。 2：命令或数据格式错误。3：服务器忙

    uint8_t rx_ack_code = pb_ack_decode(istream);

     logout("ota_process_report_rx ack=%d  ", tlv_ack_ok);
    if( tlv_ack_ok == rx_ack_code)
    {//主动上报OTA升级进度 C7H 应答 正常

       logout("ack ok\n");
    }else{
        logout("ack error\n");       
    }

return 0; //返回0,主动上报的应答不需要应答服务器

}





/**
 * @brief 北向tlv-pb协议接收处理，src为消息来源,暂不支持重入
 * 
 * @param rxbuf
 * @param rxlen 
 * @param src 
 */
 ///////------上行协议的核心处理句柄----------------------
void tlv_recv_handler(uint8_t*rx_flag, uint8_t*rxbuf, uint16_t rxlen, uint8_t src)//
{

    tlv_head_t* phead_rx = NULL;
    uint8_t* pb_rx = NULL;
    tlv_t*   pf_rx = NULL;  
    uint16_t pb_rxlen ;  
    pb_istream_t istream;

    uint16_t txlen = 0;
    tlv_t*   pf_tx = NULL;
    uint8_t* pb_tx = NULL;
    pb_ostream_t ostream ;

/////-------dan-------------////
    if( ( NULL == rx_flag) || ( NULL == rxbuf) ) 
    {
        return;	
    }

	
   if( 0 == *rx_flag)
   {
      return;	
   }

    *rx_flag = 0;

/////-------dan-------------////
     LOG_NAME();

     pf_rx = tlv_check(rxbuf, rxlen);  //校验数据帧

    if(pf_rx == NULL)
    { //校验失败
        cm_log_printf(0, "tlv check failed\n");
        return;
    }

     hplat_comm.last_conn_time = get_runtime();     //更新平台通信接收时间

     phead_rx = (tlv_head_t*)pf_rx->data;
     pb_rx = pf_rx->data + sizeof(tlv_head_t);


     pb_rxlen = pf_rx->body_lenH ;  
     pb_rxlen = (pb_rxlen << 8 ) | pf_rx->body_lenL;   

     istream = pb_istream_from_buffer(pb_rx, pb_rxlen);


     txlen = 0;  //默认,不需要应答服务器
     pf_tx = (tlv_t*)tlv_txbuf;
     pb_tx = pf_tx->data + sizeof(tlv_head_t);
     ostream = pb_ostream_from_buffer(pb_tx, TLV_TXPB_SIZE);



#if  PROTO_PLAT_PRINT_ENABLE      //本文件调试打印使能
    printf("rx frame:\n");  
    printf("head=%02x\n", pf_rx->head);     //帧头
    printf("ver=%d\n", phead_rx->version);   
    printf("seq=%d\n", phead_rx->seq);    
    printf("cmd=%02x\n",phead_rx->cmd);

   if( phead_rx->flag.dir )
   {
     printf("server->client\n");////1=server->client
   }else{
     printf("client->servert\n");  //// 0=client->server
   }

   if( phead_rx->flag.AES )
   {
     printf("ciphertext\n");//1=报文体经过AES 算法加密
   }else{
     printf("plaintext\n");  //// 0=明文---未经加密
   }

   if( phead_rx->flag.net )
   {
     printf("Node\n");////1=设备直接联网 
   }else{
     printf("Gateway\n");  //// Bit2：0=网关集中器联网
   }

   //UartPrintHex2String_NoSpace( &(buf->data[buf->head_len]), body_len);

#endif



    txlen = 0 ;  //默认,不需要应答服务器

  switch(phead_rx->cmd)
  {
    /*集中器基础命令*/

    case 0xF0://登陆 
        pb_login_rx(&istream);
    break;
    
    case 0xF1://心跳
        pb_heart_rx(&istream);
    break;
    
    case 0xF2://复位
        txlen = pb_reset_rx(&istream, &ostream);
    break;
    
    case 0xF3://查询服务ip端口
        txlen = pb_get_server_addr(&istream, &ostream);
    break;

    case 0xF4://设置服务ip端口
        txlen = pb_set_server_addr(&istream, &ostream);
    break;
    
    case 0xF5://查询软硬件信息
        txlen = pb_get_dev_info(&istream, &ostream);
    break;

    case 0xF6://模式查询/切换
        txlen = pb_sys_mode(&istream, &ostream);
    break;

    case 0xF7://
        txlen = pb_get_timestamp(&istream, &ostream);
    break;

    case 0xF8://
        txlen = pb_set_timestamp(&istream, &ostream);
    break;

    case 0xF9://
        txlen = pb_get_timezone(&istream, &ostream);
    break;

    case 0xFA://
        txlen = pb_set_timezone(&istream, &ostream);
    break;

    case 0xFB://
        txlen = pb_plat_dbg(&istream, &ostream);
    break;

    case 0xFC://下发调试数据，注意这里并非pb数据
        plat_dbg_down(phead_rx->seq, pb_rx, pb_rxlen, pf_tx);  //txbuf
    break;

    case 0xFD://上报调试数据ack
    break;

    /*网关文件命令*/
    case 0x10://读文件目录
        txlen = pb_get_file_list(&istream, &ostream);
    break;

    case 0x11://指定要读的文件
        txlen = pb_get_file_set(&istream, &ostream);
    break;

    case 0x12://上传文件ack
        logfile_upload_ack(pb_rx[0]);
    break;

    case 0x13://下载文件-暂不支持
        txlen = pb_download_file_set(&istream, &ostream);
    break;

    case 0x14://删除文件-暂不支持
        txlen = pb_del_file(&istream, &ostream);
    break;



    /*网关子设备管理*/
    case 0x60://下发子设备拓扑 
    {
        txlen = pb_set_subdev_topo(&istream, &ostream);
    }  break;


    case 0x61://删除子设备拓扑
    {
        txlen = pb_del_subdev_topo(&istream, &ostream);
    }  break;
   
    case 0x62://查询子设备拓扑
    {
        pb_get_subdev_topo(pf_rx, pf_tx);  
        txlen = 0 ;  //不需要应答服务器
    } break;
    

    case 0x63://保存子设备拓扑
    {
        txlen = pb_save_subdev_topo(&istream, &ostream);  
    }  break;


    case 0x64://拉取子设备拓扑=====7.3.2.5拉取网关子设备拓扑 64H===   // 仅当集中器升级后，若集中器本身无任何子设备时，可向平台主动拉取子设备拓扑   
    {
       // txlen = pb_set_subdev_topo(pf_rx, pf_tx);
         pb_set_subdev_topo(&istream, &ostream);  //集中器主动向服务器拉取数据，不需要应答??????
         txlen = 0 ;
    }  break;


    case 0x65://读子设备属性
        txlen = pb_get_dev_attr(&istream, &ostream);
    break;

    case 0x66://写子设备属性
        txlen = pb_set_dev_attr( &istream, &ostream, phead_rx->seq ); //phead_rx->seq ,  seq
    break;

    case 0x67://上报设备属性
    break;

    /*定时任务*/
    case 0x90://下发定时任务
        txlen = pb_add_time_task(&istream, &ostream);
    break;

    case 0x91://查询
        pb_get_time_task(pf_rx, pf_tx);
    break;

    case 0x92://删除
        txlen = pb_del_time_task(&istream, &ostream);
    break;


    /*ota 固件升级指令*/
    case 0xC0://获取空间
        txlen = pb_ota_get_space(&istream, &ostream);
    break;

    case 0xC1://文件信息
        txlen = ota_file_info(pb_rx, pb_tx);
    break;

    case 0xC2://文件传输
        txlen = ota_file_recv(pb_rx, pb_rxlen, pb_tx);
    break;

    case 0xC3://文件传输结束
        txlen = ota_file_finish(pb_rx, pb_tx);
    break;

    case 0xC4://平台查询子节点升级进度C4H
    {
        txlen = ota_file_finish(pb_rx, pb_tx);
    } break;



    case 0xC6://平台通知设备启动OTA升级 C6H
    {
        txlen = pb_ota_http_start(&istream, pb_tx, phead_rx->seq ); 
    } break;

    case 0xC7://设备主动上报OTA升级进度 C7H
    {
        txlen = pb_ota_http_process_report_rx(&istream, &ostream);
    } break;


    default:
        txlen = tlv_unsupported(pf_tx, phead_rx->cmd);
        plat_send( (uint8_t*)pf_tx, txlen);
    return;
    //case 0xC4:
    //break;
    }



   printf("pb_tx ready\n"); 

    if(txlen)
    {
        txlen = tlv_seal(pf_tx, phead_rx->cmd, phead_rx->seq, txlen);
        plat_send( (uint8_t*)pf_tx, txlen);
    
    }
}




/******************************************************************************************************************************************** */


/**
 * @brief 登陆帧封包F0
 * 
 * @param buf 
 * @return uint16_t 登陆帧长度
 */
uint16_t tlv_login(uint8_t* buf)
{
    tlv_t* pf = (tlv_t*)buf;

    uint8_t* pb = pf->data + sizeof(tlv_head_t);/////只对data数据域部分进行序列化

    LoginUp login;

    memset(&login, 0, sizeof(LoginUp));

    LOG_NAME();


    //sprintf(login.softver,"v%04x-%s",app_get_ver(),__DATE__);

    //hwVer=1.0.0
    //swVer=2.4.18
    //sprintf(login.softver,"v%04x-sw%s-hw%s-%s", app_get_ver(),get_gateway_swVer(), get_gateway_hwVer(),__DATE__);//softver=v1001-sw2.6.0t-hw1.0.0-Nov 24 20289860405191830308726
    sprintf(login.softver,"v%04x-sw%s-hw%s", app_get_ver(),get_gateway_swVer(), get_gateway_hwVer());

    sprintf(login.simcard,"%s",hcat1.CCID);

 //   memcpy(login.mac, &hcat1.IMEI[3],12);//IMEI低12位做mac，用于平台管理
//    memcpy(login.mac, &platform_info.gw_id[3],12);//PLC MAC 低12位做mac，用于平台管理  "gatewayId":"gw_1C32830600E6"
// "gatewayId":"gw_1C3283005078" login.mac=283005078

     //memcpy(login.mac, &platform_info.gw_id[0],12); ////PLC MAC 做 CCT mac gw_id=1C3283005078,ip=110.41.4.96,gw_id=10096,
////login.mac=1C3283005078

 //   memcpy(login.mac, get_gateway_id()+3,12);  //get_gateway_id()+3
    memcpy(login.mac, get_plc_mac(),12); 

    //sprintf(login.mac,"%s","FFFFFFFFFFFF");
  //  login.sub_type = CTK_PID;////子设备类型	设备直接联网时有效（见附录A）;网关集中器登录时该值填0。
    login.sub_type = CCT_PID;   //----dan----


#if  PROTO_PLAT_PRINT_ENABLE      //本文件调试打印使能
    printf("login.mac=%s\n",login.mac);
    printf("cct login\n");
    printf("softver=%s\n",login.softver);//softver=v1001-sw2.6.0t-hw1.0.0-Nov 24 20289860405191830308726
    printf("simcard=%s\n",login.simcard);
#endif


    pb_ostream_t enc_stream;
	//enc_stream = pb_ostream_from_buffer(pb, sizeof(LoginUp) + 50);  ////???为什么加50
  	enc_stream = pb_ostream_from_buffer(pb, sizeof(LoginUp) + 16);  ////???额外增加的字符串的结束符???
    if(pb_encode(&enc_stream, LoginUp_fields, &login))////////对data数据域部分进行序列化
    {
        return tlv_seal(pf, 0xF0, seq++, enc_stream.bytes_written);////只对帧头进行封装---数据域不变---最后加上校验
    }
    return 0;
}



/**
 * @brief 心跳F1
 * 
 * @param buf 
 * @return uint16_t 
 */
uint16_t tlv_heartbeat(uint8_t* buf)
{
    return tlv_seal((ptlv_t)buf, 0xF1, seq++, 0);//	数 据 域：无 
}



 

/**
 * @brief 7.3.2.5拉取网关子设备拓扑 64H
 * // 	命 令 码：64H
 * // 	功    能：仅当集中器升级后，若集中器本身无任何子设备时，可向平台主动拉取子设备拓扑
 * // 	方    向：D -> S
 * //	数 据 域：无 
 * //	响 应 帧 PROTO结构如下：
 * // 	message SendSubDeviceDown {}，详见7.3.2.1
 * // 	说明：
 * // 该命令实现后，可以做到升级无缝衔接。
 * // ??????集中器收到数据是否需要应答??????   
 * 
 * @param buf 
 * @return uint16_t 
 */
uint16_t tlv_get_topo_from_server(uint8_t* buf)
{
    return tlv_seal((ptlv_t)buf, 0x64, seq++, 0);  ////	数 据 域：无 
}






// message DeviceOtaProgressUp {
//     uint32 err_code=1;
//     uint32 stage=2;
//     uint32 step=3;
// }

// 1	err_code	错误码	0： 执行成功； 非0：执行失败的错误码
// 2	stage	升级阶段	1： 集中器从平台下载文件
                        // 2： 集中器文件校验
                        // 3： 集中器烧写Flash
                        // 4： 集中器传输文件给子设备
                        // 5： 子设备文件校验
                        // 6： 子设备烧写Flash
// 3	step	升级进度	升级进度百分比：1~100的整数 （当升级进度=100%且错误码=0时表示升级成功）


/**
 * @brief 设备主动上报OTA升级进度 C7H
 * 
 * @param buf 
 * @return uint16_t 发送给服务器帧长度
 */
uint16_t pb_ota_http_process_report(uint8_t* buf,uint8_t err_code, uint8_t stage, uint8_t percent)
{
    tlv_t* pf = (tlv_t*)buf;

    uint8_t* pb = pf->data + sizeof(tlv_head_t);   /////只对data数据域部分进行序列化

    DeviceOtaProgressUp  ota_progress_up;   

    memset(&ota_progress_up, 0, sizeof(DeviceOtaProgressUp));

    LOG_NAME();

    ota_progress_up.err_code = err_code;  
    ota_progress_up.stage    = stage;  
    ota_progress_up.step     = percent;  

#if  PROTO_PLAT_PRINT_ENABLE      //本文件调试打印使能
    printf("ota_progress err_code=%d,stage=%d,percent=%d,\n",ota_progress_up.err_code,ota_progress_up.stage,ota_progress_up.step );

#endif


    pb_ostream_t enc_stream;
	enc_stream = pb_ostream_from_buffer(pb, sizeof(DeviceOtaProgressUp) );
    if( pb_encode(&enc_stream, DeviceOtaProgressUp_fields, &ota_progress_up))////////对data数据域部分进行序列化
    {
        return tlv_seal(pf, PB_CMD_OTA_HTTP_PROCESS, seq++, enc_stream.bytes_written);      ////只对帧头进行封装---数据域不变---最后加上校验
    }else{
        return 0;  //发送数据长度设为0
    }

}




/******************************************************************************************************************************************** */

/**
 * @brief 
 * 
 * @param data 
 * @param len 
 * @return ptlv NULL=failed， other，pointer of tlv frame    NULL=失败
 */
ptlv_t tlv_check(uint8_t* data, uint16_t len)
{
    uint16_t data_len,body_len;

/////-------dan-------------////
     LOG_NAME();

    if(data == NULL)
    {
        logout("error:data=null\n");
        return NULL;
    }

    ptlv_t pf = (ptlv_t)data;

    if(pf->head != TLV_HEAD )
    { //检测数据报格式---避免非法数据帧
        logout("err:packet error head=0x%02x\n",pf->head);     
        return NULL;
    }

    if( len < sizeof(tlv_t) )
    { //检测数据报格式---避免非法数据帧
        logout("err:packet error len=%d\n",len); 
        return NULL;
    }



  //  uint16_t data_len = pf->head_len + (pf->body_lenH<<8|pf->body_lenL);  //bug

 //    data_len = pf->body_lenH;  //debug
 //    data_len = pf->head_len + ((data_len<<8) |pf->body_lenL);  //debug 

     body_len = pf->body_lenH;  
     body_len =  (body_len<<8) |pf->body_lenL  ;   
     data_len = pf->head_len + body_len;   


    if(data_len > TLV_MAX_LEN)
    { //检测数据长度--避免缓冲区溢出

        logout("err:packet data_len=%d outof rang > TLV_MAX_LEN(%d)\n",data_len,TLV_MAX_LEN);      
        return NULL;
    }


#if 0
    uint16_t crc_cal = iotv30_crc16_modbus(data, len-2);
    if(crc_cal != (pf->data[data_len] << 8 | pf->data[data_len + 1]) && 
       crc_cal != (pf->data[data_len + 1] << 8 | pf->data[data_len]))return NULL;
#endif

      logout("rx_crc_1=0x%02x,rx_crc_2=0x%02x\n",data[len-2],data[len-1] );  
      uint16_t crc_cal = iotv30_crc16_modbus(data, len);
     if( crc_cal )
     {
        //packet check error: cal_crc=8367,rx_crc_1=83,rx_crc_2=67

        logout("packet check error: cal_crc=0x%04x\n",crc_cal);   
        return NULL; 

     }else{
        logout("packet check ok!\n");
        logout("payload data:");
        //UartPrintHex2String(&(pf->data[pf->head_len]) ,body_len );
         UartPrintHex2String_NoSpace(&(pf->data[pf->head_len]) ,body_len );
        return pf;
     }

}




/**
 * @brief 不支持的指令
 * 
 * @param buf 
 * @param cmd 
 * @return uint16_t 
 */
uint16_t tlv_unsupported(ptlv_t buf, uint8_t cmd)
{
    buf->head = TLV_HEAD;
    buf->head_len = sizeof(tlv_head_t);
    buf->body_lenH = 0;
    buf->body_lenL = 0;
    
    ptlv_head_t ph = (ptlv_head_t)buf->data;
    ph->version = TLV_VERSION;
    ph->seq = seq;
    ph->flag.AES = 0;
    ph->flag.dir = 0;
    ph->flag.net = tlv_head2_net_CCT;
    ph->flag.unsupported = 1;
    ph->flag.res = 0;
    ph->cmd = cmd;

    uint16_t totol_len = sizeof(tlv_t) + sizeof(tlv_head_t);
    uint16_t crc = iotv30_crc16_modbus((uint8_t*)buf, totol_len);

    // buf->data[sizeof(tlv_head_t) + body_len] = (uint8_t)(crc >> 8);
    // buf->data[sizeof(tlv_head_t) + body_len + 1] = (uint8_t)crc;

    buf->data[sizeof(tlv_head_t) + 0] = (uint8_t)crc;              //先发送低字节
    buf->data[sizeof(tlv_head_t) + 0 + 1] = (uint8_t)(crc >> 8);   //再发送高字节

    return totol_len + 2;
}


/**
 * @brief body部分已经写好，调用本函数填写头部， 增加校验码[避免复制来复制去]
 * ////只对帧头进行封装---数据域不变---最后加上校验

 * @param buf 
 * @param cmd 
 * @param seq 
 * @param body_len 
 * @return uint16_t 最终帧的长度

 */
uint16_t tlv_seal(ptlv_t buf, uint8_t cmd, uint8_t seq, uint16_t body_len)
{
// 报文头内容定义格式如下：
// 编号	长度	名称	说明
// 1	1字节	协议版本号	通讯协议主版本号，例如v3.0.0版本号为0x03
// 1	1字节	序列号  	报文帧序号。
// 报文的发送方需保证连续发送的报文其序号依次递增；响应方回复时需保证与发送方保持一致。
// 2	1字节	特殊标识 	Bit0：通信方向。 1=server->client, 0=client->server
// Bit1：1=报文体经过AES 算法加密
// Bit2：0=网关集中器联网，1=设备直接联网   
// 3	1字节	命令码	控制或应答指令。
// 注意：心跳包的报文头到此结束，共4个字节，后面的扩展内容不传输，目的为了节省流量。
// 以下为扩展内容
// 4	4字节	时间戳	当前系统时间戳，精确到秒。
// 响应方回复时需保证与发送方保持一致。


 ////////只对帧头进行封装---数据域不变---最后加上校验
    buf->head = TLV_HEAD;     //帧头
    buf->head_len = sizeof(tlv_head_t);
    buf->body_lenH = (uint8_t)(body_len >> 8);
    buf->body_lenL = (uint8_t)body_len;
    
    ptlv_head_t ph = (ptlv_head_t)buf->data;
    ph->version = TLV_VERSION;
    ph->seq = seq;//序列号 ,依次递增,保证与发送方保持一致

    ph->flag.AES = 0;// Bit1：1=报文体经过AES 算法加密
    ph->flag.dir = 0;// 0=client->server

   // ph->flag.net = 1;// Bit2：0=网关集中器联网，1=设备直接联网  
    ph->flag.net = tlv_head2_net_CCT;// Bit2：0=网关集中器联网，1=设备直接联网  

    ph->flag.unsupported = 0;
    ph->flag.res = 0;
    ph->cmd = cmd;

    uint16_t totol_len = sizeof(tlv_t) + sizeof(tlv_head_t) + body_len;
    uint16_t crc = iotv30_crc16_modbus((uint8_t*)buf, totol_len);

    // buf->data[sizeof(tlv_head_t) + body_len] = (uint8_t)(crc >> 8);
    // buf->data[sizeof(tlv_head_t) + body_len + 1] = (uint8_t)crc;

    buf->data[sizeof(tlv_head_t) + body_len] = (uint8_t)crc;
    buf->data[sizeof(tlv_head_t) + body_len + 1] = (uint8_t)(crc >> 8);


#if  PROTO_PLAT_PRINT_ENABLE      //本文件调试打印使能

    printf("%s\n",__FUNCTION__);
    printf("tx frame:\n"); 
    printf("head=0x%02x\n", buf->head);     //帧头
    printf("ver=%d\n", ph->version);   
    printf("seq=%d\n", ph->seq);    
    printf("cmd=0x%02x\n",ph->cmd);

   if( ph->flag.dir )
   {
     printf("server->client\n");////1=server->client
   }else{
     printf("client->servert\n");  //// 0=client->server
   }

   if( ph->flag.AES )
   {
     printf("ciphertext\n");//1=报文体经过AES 算法加密
   }else{
     printf("plaintext\n");  //// 0=明文---未经加密
   }

     printf("device:");
   if( ph->flag.net )
   {
     printf("Node\n");////1=设备直接联网 
   }else{
     printf("Gateway\n");  //// Bit2：0=网关集中器联网
   }

   //UartPrintHex2String_NoSpace( &(buf->data[buf->head_len]), body_len);

    printf("body_data_pb_format: body_len=%d:\n",body_len); 

 #if 0 //   这部分与 TCP send 打印重复，为了提高效率，这里取消打印
     uint i,idx;

     idx=buf->head_len;
    for ( i = 0; i < body_len; i++)
    {
        printf("%02X", buf->data[idx++]); //注意这里不需要打印空格
    }
    printf("\n");
#endif  

   printf("crc=0x%04x:\n",crc); 


#endif

    return totol_len + 2;

}



/////----拷贝数据到通讯帧的数据域部分----
void tlv_pack_load_body(ptlv_t buf, uint8_t*body, uint16_t body_len)
{
    memcpy(&buf->data[sizeof(tlv_head_t)], body, body_len);
}


/////----获取通讯帧的数据域首地址的指针----
uint8_t* tlv_pack_get_body(ptlv_t buf)
{
    return &buf->data[sizeof(tlv_head_t)];
}


/**
 * @brief 打包一个tlv-pb包，注意和tlv_seal的区别
 * /////需要把数据域部分数据拷贝到发送缓冲区，数据域是非PB格式,与tlv_seal的区别是 tlv_seal不用拷贝数据，
 //它的数据提前放在了发送缓冲区里，且是PB格式的数据
 * @param buf 
 * @param cmd 
 * @param seq 
 * @param uint8_t* 数据指针
 * @param body_len 
 * @return uint16_t 最终帧的长度
 */
uint16_t tlv_pack(ptlv_t buf, uint8_t cmd, uint8_t seq, uint8_t*body, uint16_t body_len)
{
    buf->head = TLV_HEAD;
    buf->head_len = sizeof(tlv_head_t);
    buf->body_lenH = (uint8_t)(body_len >> 8);
    buf->body_lenL = (uint8_t)body_len;
    
    ptlv_head_t ph = (ptlv_head_t)buf->data;
    ph->version = TLV_VERSION;
    ph->seq = seq;
    ph->flag.AES = 0;
    ph->flag.dir = 0;
    ph->flag.net = tlv_head2_net_CCT;
    ph->flag.unsupported = 0;
    ph->flag.res = 0;
    ph->cmd = cmd;

    memcpy(&buf->data[sizeof(tlv_head_t)], body, body_len);/////需要把数据域部分数据拷贝到发送缓冲区，数据域是非PB格式

    uint16_t totol_len = sizeof(tlv_t) + sizeof(tlv_head_t) + body_len;
    uint16_t crc = iotv30_crc16_modbus((uint8_t*)buf, totol_len);

    // buf->data[sizeof(tlv_head_t) + body_len] = (uint8_t)(crc >> 8);
    // buf->data[sizeof(tlv_head_t) + body_len + 1] = (uint8_t)crc;

    buf->data[sizeof(tlv_head_t) + body_len] = (uint8_t)crc;
    buf->data[sizeof(tlv_head_t) + body_len + 1] = (uint8_t)(crc >> 8);




#if  PROTO_PLAT_PRINT_ENABLE      //本文件调试打印使能

    uint i,idx;

    printf("%s\n",__FUNCTION__);

    printf("head=%02x\n", buf->head);     //帧头
    printf("ver=%d\n", ph->version);   
    printf("seq=%d\n", ph->seq);    
    printf("cmd=0x%02x\n",ph->cmd);

   if( ph->flag.dir )
   {
     printf("server->client\n");////1=server->client
   }else{
     printf("client->servert\n");  //// 0=client->server
   }

   if( ph->flag.AES )
   {
     printf("ciphertext\n");//1=报文体经过AES 算法加密
   }else{
     printf("plaintext\n");  //// 0=明文---未经加密
   }

   if( ph->flag.net )
   {
     printf("Node\n");////1=设备直接联网 
   }else{
     printf("Gateway\n");  //// Bit2：0=网关集中器联网
   }


    printf("body_data_not_pb_format: body_len=%d:\n",body_len); 

   //UartPrintHex2String_NoSpace( &(buf->data[buf->head_len]), body_len);

     idx=buf->head_len;
    for ( i = 0; i < body_len; i++)
    {
        printf("%02X", buf->data[idx++]); //注意这里不需要打印空格
    }
    printf("\n");

    printf("crc=%04x:\n",crc); 


#endif

    return totol_len + 2;

}









// 6.3全局响应码
// 序号	响应码(ACK)	说明
// 1	01	集中器成功受理
// 2	02	命令或数据格式错误
// 3	03	集中器忙
// 4	04	终端原因不受控（终端是指网关子设备）
// 5	05	当前设备模式不支持远程控制
// 6	06	子设备ID不存在
// 7	07~15	预留（平台定义）
// 8	16~255	其它错误码（设备自定义，共240个）



  //       return pb_ack_encode(ostream, tlv_ack_ok);


void proto_pb_global_ack( uint8_t cmd,uint8_t seq,tlv_ack_e ack_code)
{
   // uint8_t  txbuf[TLV_TXBUF_SIZE];
    uint16_t txlen = 0;
    tlv_t*   pf_tx = (tlv_t*)tlv_txbuf;
    uint8_t* pb_tx = pf_tx->data + sizeof(tlv_head_t);
    pb_ostream_t ostream = pb_ostream_from_buffer(pb_tx, TLV_TXPB_SIZE);

    txlen = pb_ack_encode(&ostream, ack_code);

    printf("pb_tx ready txlen=%d\n",txlen); 

    if(txlen)
    {
        txlen = tlv_seal(pf_tx, cmd, seq, txlen);
        plat_send( (uint8_t*)pf_tx, txlen);  
    }

}